//
//  GConnection.m
//  Genesis
//
//  Created by Kristoffer Andersen on 13/03/09.
//  Copyright 2009 Kristoffer Andersen. All rights reserved.
//
//  This file is part of Genesis.
//  
//  Genesis is free software: you can redistribute it and/or modify
//  it under the terms of the GNU General Public License as published by
//  the Free Software Foundation, either version 3 of the License, or
//  (at your option) any later version.
//  
//  Genesis is distributed in the hope that it will be useful,
//  but WITHOUT ANY WARRANTY; without even the implied warranty of
//  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
//  GNU General Public License for more details.
//  
//  You should have received a copy of the GNU General Public License
//  along with Genesis.  If not, see <http://www.gnu.org/licenses/>.

#import "GConnection.h"
#import "SSHSession.h"

@implementation GConnection

@synthesize delegate;
@synthesize flag;
@synthesize password;

////////////////////////////////////////////////////////////////
//// Initializers

-(id)init
{
	self = [super init];
	//NSLog(@"Connection init...");
	[self setForwardHost:@"localhost"];
	[self setTunnelPort:22];
	[self setForwardPort:548];
	[self setServiceType:@"_afpovertcp._tcp."];
	NSArray *addrs = [[NSHost currentHost] names];
	uint32_t index = 0;
	while ([[addrs objectAtIndex:index] isEqualToString:@"localhost"] && index < [addrs count]) index++;
	[self setLocalHost:[addrs objectAtIndex:index]];
	NSLog(@"Hostname in use: %@",[addrs objectAtIndex:index]);
	[self setLocalPort:5480];
	[self setUseBonjour:YES];
    flag = [[NSCondition alloc] init];
	return self;
}

////////////////////////////////////////////////////////////////
//// Setter accessors

-(void)setConnectionName:(NSString *)newName
{
	[newName retain];
	[connectionName release];
	connectionName = newName;
}

-(void)setTunnelHost:(NSString *)newTunnelHost
{
	[newTunnelHost retain];
	[tunnelHost release];
	tunnelHost = newTunnelHost;
}

-(void)setTunnelPort:(unsigned int)newTunnelPort
{
	tunnelPort = newTunnelPort;
}

-(void)setForwardHost:(NSString *)newForwardHost
{
	[newForwardHost retain];
	[forwardHost release];
	forwardHost = newForwardHost;
}

-(void)setForwardPort:(unsigned int)newForwardPort
{
	forwardPort = newForwardPort;
}

-(void)setLocalHost:(NSString *)newLocalHost
{
	[newLocalHost retain];
	[localHost release];
	localHost = newLocalHost;
}

-(void)setLocalPort:(unsigned int)newLocalPort
{
	localPort = newLocalPort;
}

-(void)setUseBonjour:(bool)newValue
{
	useBonjour = newValue;
}

-(void)setServiceName:(NSString *)newServiceName
{
	[newServiceName retain];
	[serviceName release];
	serviceName = newServiceName;
}

-(void)setServiceType:(NSString *)newServiceType
{
	[newServiceType retain];
	[serviceType release];
	serviceType = newServiceType;
}

-(void)setServiceRecord:(NSString *)newServiceRecord
{
	[newServiceRecord retain];
	[serviceRecord release];
	serviceRecord = newServiceRecord;
}

-(void)setTunnelUsername:(NSString *)newUsername
{
	[newUsername retain];
	[tunnelUsername release];
	tunnelUsername = newUsername;
}

////////////////////////////////////////////////////////////////
//// Getter accessors

-(NSString *)connectionName
{
	//NSLog(@"return connectionName called! (%@)",self);
	return connectionName;
}

-(NSString *)tunnelHost
{
	return tunnelHost;
}

-(unsigned int)tunnelPort
{
	return tunnelPort;
}

-(NSString *)forwardHost
{
	return forwardHost;
}

-(unsigned int)forwardPort
{
	return forwardPort;
}

-(NSString *)localHost
{
	return localHost;
}

-(unsigned int)localPort
{
	return localPort;
}

-(bool)useBonjour
{
	return useBonjour;
}

-(NSString *)serviceName
{
	return serviceName;
}

-(NSString *)serviceType
{
	return serviceType;
}

-(NSString *)serviceRecord
{
	return serviceRecord;
}

-(NSString *)tunnelUsername
{
	return tunnelUsername;
}

-(bool)prepared
{
	return prepared;
}
-(bool)connected
{
	return connected;
}
-(bool)interrupted
{
	return interrupted;
}
-(bool)connecting
{
	return connecting;
}
-(NSMutableString *)console
{
	return consoleCache;
}

////////////////////////////////////////////////////////////////
//// Connection Methods

-(int)prepareConnection
{
	if (tunnelHost == nil) return 1;
	if (tunnelPort == 0) return 2;
	if (forwardHost == nil) return 3;
	if (forwardPort == 0) return 4;
	if (localHost == nil) return 5;
	if (localPort == 0) return 6;
	if (tunnelUsername == nil) return 7;
	
	//All params set!
	//TODO: Get PW from keychain...
	
	//Prepare the tunnel object
	NSHost *host = [NSHost hostWithName:tunnelHost];
    NSString *remoteIP = [GConnection getv4Address:host];
	session = [[SSHSession alloc] initWithRemoteIP:remoteIP remotePort:tunnelPort];
    [session setUser:tunnelUsername];
    [session setPasswordDelegate:self];
	//tunnel = [[shhTunnel alloc] init];
	//[tunnel initSSHTunnel:tunnelUsername host:tunnelHost portLocalForward:localPort portRemoteForward:forwardPort forwardHost:forwardHost forwardDest:localHost];
	
	//Setup a connection observer
	//[[NSNotificationCenter defaultCenter] addObserver:self selector:@selector(tunnelSuccesNotification:) name:@"ConnectionSucces" object:nil];
	//[[NSNotificationCenter defaultCenter] addObserver:self selector:@selector(tunnelInteruptedNotification:) name:@"ConnectionInterupted" object:nil];
	//[[NSNotificationCenter defaultCenter] addObserver:self selector:@selector(tunnelDidPostTextNotification:) name:@"ConnectionDidPostText" object:nil];
	//[[NSNotificationCenter defaultCenter] addObserver:self selector:@selector(connectionDidEndNotification:) name:NSTaskDidTerminateNotification object:nil];
	
	//Clear cache
	[consoleCache release];
	consoleCache = [[NSMutableString alloc] init];
	
	prepared = YES;
	return 0;
}

+(NSString*) getv4Address:(NSHost*)host
{
    NSArray *allAddresses = [host addresses];
    NSPredicate *pred = [NSPredicate predicateWithFormat:@"SELF MATCHES '^(?:[0-9]{1,3}\.){3}[0-9]{1,3}$'"];
    NSArray *ipv4Addresses = [allAddresses filteredArrayUsingPredicate:pred];
    return [ipv4Addresses objectAtIndex:0];
}

-(void)createConnection
{
    NSAutoreleasePool *pool = [[NSAutoreleasePool alloc] init];

	connecting = YES;
	NSLog(@"Connection: connecting: %i",connecting);
	connected = NO;
	interrupted = NO;
	prepared = NO;
	//[tunnel connectSSH];
	//[self tunnelSuccesNotification:nil];
    [session connect];
    NSHost *listenHost = [NSHost hostWithName:localHost];
    NSString *localIP = [GConnection getv4Address:listenHost];
    NSHost *remoteHost = [NSHost hostWithName:forwardHost];

    SSHTunnel *t = [session createTunnelWithLocalIP:localIP
                                          localPort:localPort 
                                           remoteIP:[remoteHost address] 
                                         remotePort:forwardPort];
    if([t activate] != 0) {
        [self performSelectorOnMainThread:@selector(tunnelInteruptedNotification:)
                               withObject:nil
                            waitUntilDone:NO];
    }
    else {
        [self performSelectorOnMainThread:@selector(tunnelSuccesNotification:)
                               withObject:nil
                            waitUntilDone:NO];
    }

    [pool release];
}

-(void)createConnectionBlocking
{
	
}

-(void)terminateConnection
{
	connecting = NO;
	connected = NO;
	prepared = NO;
	if (bService) {
		[bService stopService];
		[bService release];
		bService = nil;
	}
	[tunnel terminateSSH];
}


////////////////////////////////////////////////////////////////
//// Observers

-(void)tunnelSuccesNotification:(NSNotification *)aNotification
{
	connected = YES;
	NSLog(@"Connection: connecting: %i",connecting);
	NSLog(@"Connection: connected: %i",connected);
	connecting = NO;
	prepared = NO;
	//Init bonjour
	if (useBonjour) {
		bService = [[tunBonjour alloc] init];
		[bService createService:serviceName type:serviceType port:localPort host:localHost];
	}
	if (delegate != nil && [delegate respondsToSelector:@selector(connectionSucces:)])
		[delegate connectionSucces:self];
	
}

-(void)tunnelInteruptedNotification:(NSNotification *)aNotification
{
	
	NSBeep();
	
	//[tunnel terminateSSH];
	interrupted = YES;
	
	//If loaded, stop bonjour
	if (bService) {
		[bService stopService];
		[bService release];
		bService = nil;
	}
	
	if (delegate != nil && [delegate respondsToSelector:@selector(connectionInterupted:)])
		[delegate connectionInterupted:self];
}

/* Request password from delegate, returns a string or nil, if not available*/
-(NSString*)requestPassword
{
	if (delegate != nil && [delegate respondsToSelector:@selector(requestPasswordForConnection:)])
		return [delegate requestPasswordForConnection:self];
	else return nil;
}

-(void)tunnelDidPostTextNotification:(NSNotification *)aNotification
{
	[consoleCache appendString:[aNotification object]];
	//printf("%s",[[aNotification object] cStringUsingEncoding:NSUTF8StringEncoding]);
}

-(void)connectionDidEndNotification:(NSNotification *)aNotification
{
	connected = NO;
	connecting = NO;
	connected = NO;
	prepared = NO;
	interrupted = YES;
	//If loaded, stop bonjour
	if (bService) {
		[bService stopService];
		[bService release];
		bService = nil;
	}
	NSLog(@"%@ disconnected...",[self connectionName]);
}

////////////////////////////////////////////////////////////////
//// NSCoding Protocol
-(id)initWithCoder:(NSCoder *)aDecoder
{
	self = [super init];
	//NSLog(@"Connection init...");
	[self setForwardHost:[aDecoder decodeObjectForKey:@"forwardHost"]];
	[self setTunnelPort:22];
	[self setForwardPort:[aDecoder decodeIntegerForKey:@"forwardPort"]];
	[self setServiceType:[aDecoder decodeObjectForKey:@"serviceType"]];
	NSArray *addrs = [[NSHost currentHost] names];
	uint32_t index = 0;
	while (![[addrs objectAtIndex:index] isEqualToString:[aDecoder decodeObjectForKey:@"localHost"]] && index < [addrs count]) index++;
	[self setLocalHost:[addrs objectAtIndex:index]];
	NSLog(@"Hostname in use: %@",[addrs objectAtIndex:index]);
	[self setLocalPort:[aDecoder decodeIntegerForKey:@"localPort"]];
	[self setUseBonjour:[aDecoder decodeBoolForKey:@"useBonjour"]];
	
	[self setConnectionName:[aDecoder decodeObjectForKey:@"connectionName"]];
	[self setTunnelHost:[aDecoder decodeObjectForKey:@"tunnelHost"]];
	[self setTunnelUsername:[aDecoder decodeObjectForKey:@"tunnelUsername"]];
	[self setServiceName:[aDecoder decodeObjectForKey:@"serviceName"]];
	[self setServiceRecord:[aDecoder decodeObjectForKey:@"serviceRecord"]];
	
	return self;
}

-(void)encodeWithCoder:(NSCoder *)aCoder
{
	[aCoder encodeObject:connectionName forKey:@"connectionName"];
	[aCoder encodeObject:tunnelHost forKey:@"tunnelHost"];
	[aCoder encodeInteger:tunnelPort forKey:@"tunnelPort"];
	[aCoder encodeObject:forwardHost forKey:@"forwardHost"];
	[aCoder encodeInteger:forwardPort forKey:@"forwardPort"];
	[aCoder encodeObject:localHost forKey:@"localHost"];
	[aCoder encodeInteger:localPort forKey:@"localPort"];
	
	[aCoder encodeBool:useBonjour forKey:@"useBonjour"];
	[aCoder encodeObject:serviceName forKey:@"serviceName"];
	[aCoder encodeObject:serviceType forKey:@"serviceType"];
	[aCoder encodeObject:serviceRecord forKey:@"serviceRecord"];
	[aCoder encodeObject:tunnelUsername forKey:@"tunnelUsername"];
}

/**********************/
-(NSString*) passwordCallback
{
    [flag lock];
	password = nil;
    [delegate getPassword:self];
    //[delegate performSelectorOnMainThread:@selector(getPassword:)
    //                           withObject:self
    //                        waitUntilDone:NO];
    while (password == nil) [flag wait];
    [delegate unlockPassword];
    [flag unlock];
    return password;
}

@end
